﻿using MyCryptoChat.BusinessEntities;
using MyCryptoChat.ViewModels;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;

namespace MyCryptoChat.Controllers
{
    public class HomeController : Controller
    {
        private static bool _hasLoadedChatrooms = false;

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult NewRoom(int nbMinutesToLive, bool? selfDestroys)
        {
            string id = HashInMD5(Guid.NewGuid().ToString() + Constants.LocalSeed).Substring(0, Constants.ROOM_ID_LENGTH);

            if (nbMinutesToLive < Constants.NB_MIN_MINUTES_TO_LIVE)
            {
                nbMinutesToLive = Constants.NB_MIN_MINUTES_TO_LIVE;
            }
            if (nbMinutesToLive > Constants.NB_MAX_MINUTES_TO_LIVE)
            {
                nbMinutesToLive = Constants.NB_MAX_MINUTES_TO_LIVE;
            }

            var chatRoom = new ChatRoom()
                {
                    ChatLines = new List<ChatMessage>(),
                    DateCreation = DateTime.UtcNow,
                    DateLastNewMessage = DateTime.UtcNow,
                    NbMinutesToLive = nbMinutesToLive,
                    ConnectedUsers = new List<ChatUser>(),
                    NoMoreThanOneVisitor = selfDestroys.HasValue && selfDestroys.Value,
                    UserId = selfDestroys.HasValue && selfDestroys.Value ? GetHashFromIp() : ""
                };

            HttpContext.Application.Add(id, chatRoom);

            return
                Redirect(Url.RouteUrl(new { controller = "Home", action = "Room", id = id }));
        }

        public ActionResult Room(string id)
        {
            return View(model: id);
        }

        public ActionResult GetMessages(string roomId, DateTime dateLastGetMessages, int nbIps)
        {
            // if the server was shut down, we reload the conversations
            if (!_hasLoadedChatrooms)
            {
                LoadChatrooms();
            }

            if (RoomExists(roomId))
            {
                var chatRoom = HttpContext.Application[roomId] as ChatRoom;
                if (chatRoom != null)
                {
                    string ip = HttpContext.Request.ServerVariables["REMOTE_ADDR"];

                    var now = DateTime.UtcNow;

                    var currentUserId = GetHashFromIp();

                    if (!chatRoom.NoMoreThanOneVisitor)
                    {
                        chatRoom.ConnectedUsers.RemoveAll(
                            e => e.DateLastSeen.AddSeconds(Constants.NB_SECONDS_USER_TO_BE_DISCONNECTED) < now);
                    }

                    var connectedIp = chatRoom.ConnectedUsers.FirstOrDefault(e => e.Id == currentUserId);
                    if (connectedIp != null)
                    {
                        connectedIp.DateLastSeen = now;
                    }
                    else
                    {
                        connectedIp = new ChatUser() { DateLastSeen = now, Id = currentUserId };
                        chatRoom.ConnectedUsers.Add(connectedIp);
                    }

                    if (chatRoom.NoMoreThanOneVisitor && chatRoom.ConnectedUsers.Count(e => e.Id != chatRoom.UserId) > 1)
                    {
                        chatRoom = null;
                        HttpContext.Application.Remove(roomId);
                        return Json("destroyed");
                    }

                    if (dateLastGetMessages < chatRoom.DateLastNewMessage)
                    {
                        var chatLines = chatRoom.ChatLines;
                        return
                            Json(new
                            {
                                dateLastGetMessages = now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture),
                                chatLines = chatLines.Skip(Math.Max(0, chatLines.Count() - Constants.NB_MESSAGES_TO_KEEP))
                                                     .Take(Constants.NB_MESSAGES_TO_KEEP)
                                                     .ToList(),
                                nbIps = chatRoom.ConnectedUsers.Count
                            });
                    }
                    else if (nbIps != chatRoom.ConnectedUsers.Count)
                    {
                        return Json(new { nbIps = chatRoom.ConnectedUsers.Count });
                    }
                    else
                    {
                        return Json("noNew");
                    }
                }
            }
            return Json("noRoom");
        }

        public ActionResult SendMessage(string roomId, string user, string message)
        {
            if (RoomExists(roomId))
            {
                var chatRoom = HttpContext.Application[roomId] as ChatRoom;
                if (chatRoom != null)
                {
                    chatRoom.ChatLines.Add(new ChatMessage()
                    {
                        Message = message,
                        Hash = GetHashFromIp(),
                        User = user,
                        Date = DateTime.UtcNow.ToString("HH:mm")
                    });

                    chatRoom.DateLastNewMessage = DateTime.UtcNow;

                    return Json(true);
                }
            }
            return Json(false);
        }

        [OutputCache(Location = OutputCacheLocation.Server, Duration = 60)]
        public ActionResult Stats()
        {
            var model = new StatsViewModel { NbChatrooms = 0, NbMessages = 0 };

            var chatrooms = new List<ChatroomStatsViewModel>();

            int idChatroom = 1;

            foreach (var key in HttpContext.Application.AllKeys)
            {
                var chatRoom = HttpContext.Application[key] as ChatRoom;
                if (chatRoom != null)
                {
                    model.NbChatrooms++;
                    int nbMessages = chatRoom.ChatLines.Count;

                    chatrooms.Add(new ChatroomStatsViewModel()
                    {
                        DateCreation = chatRoom.DateCreation,
                        DateEnd = chatRoom.DateCreation.AddMinutes(chatRoom.NbMinutesToLive),
                        Id = idChatroom,
                        NbMessages = nbMessages
                    });

                    model.NbMessages += nbMessages;

                    idChatroom++;
                }
            }

            model.Chatrooms = chatrooms;

            return View(model);
        }

        public ActionResult About()
        {
            return View();
        }

        private string GetHashFromIp()
        {
            string userIp = HttpContext.Request.ServerVariables["REMOTE_ADDR"];

            userIp += Constants.LocalSeed;

            return HashInMD5(userIp);
        }

        private string HashInMD5(string stringToHash)
        {
            MD5 md5 = MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(stringToHash);
            byte[] hashBytes = md5.ComputeHash(inputBytes);

            var stringBuilder = new StringBuilder();
            for (int i = 0; i < hashBytes.Length; i++)
            {
                stringBuilder.Append(hashBytes[i].ToString("X2"));
            }

            return stringBuilder.ToString();
        }

        private bool RoomExists(string roomId)
        {
            return HttpContext.Application[roomId] as ChatRoom != null;
        }

        private void LoadChatrooms()
        {
            string filePath = Path.Combine(HttpContext.ApplicationInstance.Server.MapPath("~/App_Data"),
                                            Constants.SAVE_CHATROOMS_FILE_NAME);

            if (System.IO.File.Exists(filePath))
            {
                var chatRooms = LoadAsBin<List<ChatRoom>>(filePath);

                if (chatRooms != null && chatRooms.Any())
                {
                    foreach (var chatRoom in chatRooms)
                    {
                        HttpContext.Application[chatRoom.Id] = chatRoom;
                    }
                }

                System.IO.File.Delete(filePath);
            }

            _hasLoadedChatrooms = true;
        }

        private static T LoadAsBin<T>(string path)
        {
            var formatter = new BinaryFormatter();
            FileStream flux = null;
            try
            {
                flux = new FileStream(path, FileMode.Open, FileAccess.Read);

                return (T)formatter.Deserialize(flux);
            }
            catch
            {
                return default(T);
            }
            finally
            {
                if (flux != null)
                    flux.Close();
            }
        }
    }
}
